"""Dogtag certificate management."""

import base64
import typing
from urllib import parse

from cki_lib import misc
from cki_lib.session import get_session
from cryptography import x509
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.serialization import pkcs7
from cryptography.x509.oid import NameOID

from . import secrets
from . import token

SESSION = get_session(__name__, headers={'Accept': 'application/json'}, raise_for_status=True)


@token.register_token('dogtag_certificate')
class DogtagCertificate(token.Token):
    """Dogtag certificates."""

    clean_active_versions = 1

    def _create_token(self, token_version: str, meta: dict[str, typing.Any]) -> dict[str, str]:
        """Create Dogtag certificates."""
        uid = meta['uid']
        password = secrets.secret(next(
            k for k, v in secrets.read_secrets_file().items()
            if misc.get_nested_key(v, 'meta/token_type') == 'ldap_password'
            and misc.get_nested_key(v, 'meta/uid') == uid))

        signing_cert_chain = '\n'.join(
            c.public_bytes(serialization.Encoding.PEM).decode('ascii').strip()
            for c in pkcs7.load_der_pkcs7_certificates(base64.b64decode(SESSION.get(
                parse.urljoin(meta['server_url'], 'ca/rest/config/cert/signing'),
            ).json()['PKCS7CertChain']))
        )

        private_key = rsa.generate_private_key(65537, 4096)

        name = x509.Name.from_rfc4514_string(meta['SubjectDN'])
        csr_builder = x509.CertificateSigningRequestBuilder().subject_name(name)
        if cns := name.get_attributes_for_oid(NameOID.COMMON_NAME):
            csr_builder = csr_builder.add_extension(
                x509.SubjectAlternativeName([x509.DNSName(typing.cast(str, cns[0].value))]),
                critical=False)
        csr = (
            csr_builder
            .sign(private_key, hashes.SHA256())
            .public_bytes(serialization.Encoding.PEM)
            .decode('ascii')
        )

        certrequest_post_url = parse.urljoin(meta['server_url'], 'ca/rest/certrequests')
        certrequest_url = SESSION.post(certrequest_post_url, json={
            'ProfileID': meta['ProfileID'],
            'Input': [{'Attribute': [
                {'name': 'cert_request_type', 'Value': 'pkcs10'},
                {'name': 'cert_request', 'Value': csr},
            ]}],
            'Attributes': {'Attribute': [
                {'name': 'uid', 'value': uid},
                {'name': 'pwd', 'value': password},
            ]},
        }).json()['entries'][0]['requestURL']

        meta['id'] = SESSION.get(certrequest_url).json()['certId']
        self._update_token(token_version, meta)

        certificate = SESSION.get(
            parse.urljoin(meta['server_url'], f'ca/rest/certs/{meta["id"]}'),
        ).json()['Encoded'].strip()

        return {
            'private_key': private_key.private_bytes(
                serialization.Encoding.PEM,
                serialization.PrivateFormat.PKCS8,
                serialization.NoEncryption(),
            ).decode('ascii').strip(),
            'certificate': certificate,
            'chain': f'{certificate}\n{signing_cert_chain}',
        }

    def _destroy_token(self, token_version: str, meta: dict[str, typing.Any]) -> None:
        """Destroy a token version."""
        # there is no API call to revoke a Dogtag certificate

    def _update_token(self, token_version: str, meta: dict[str, typing.Any]) -> None:
        """Update a token version."""
        cert = SESSION.get(parse.urljoin(meta['server_url'], f'ca/rest/certs/{meta["id"]}')).json()
        meta.update({
            'id': cert['id'],
            'created_at': misc.datetime_fromisoformat_tz_utc(cert['NotBefore']).isoformat(),
            'expires_at': misc.datetime_fromisoformat_tz_utc(cert['NotAfter']).isoformat(),
            'active': cert['Status'] == 'VALID',
            'IssuerDN': cert['IssuerDN'],
            'SubjectDN': cert['SubjectDN'],
        })

    def _validate_token(self, token_version: str, meta: dict[str, typing.Any]) -> None:
        """Validate a token version."""
        token_secret = secrets.secret(f'{self.full_token_name(token_version)}:')
        private_key = serialization.load_pem_private_key(
            token_secret['private_key'].encode('ascii'), None)
        cert = x509.load_pem_x509_certificate(token_secret['certificate'].encode('ascii'))
        if private_key.public_key().public_bytes(
            serialization.Encoding.PEM,
            serialization.PublicFormat.SubjectPublicKeyInfo,
        ) != cert.public_key().public_bytes(
            serialization.Encoding.PEM,
            serialization.PublicFormat.SubjectPublicKeyInfo,
        ):
            raise ValueError('Certificate does not match private key')
